package api

import (
	"context"
	"net/http"
	"reflect"

	"dev.ixmxm.top/rms/codes"
	"dev.ixmxm.top/rms/utils/json"
	"dev.ixmxm.top/rms/utils/log"
	"dev.ixmxm.top/rms/utils/tools"
	"github.com/gin-gonic/gin"
)

type DoFunc func(ctx context.Context, d interface{}) (interface{}, codes.Code)

type DoFunc_File func(ctx context.Context, c *gin.Context) (interface{}, codes.Code)

type DoFunc_Excel func(ctx context.Context, d interface{}, c *gin.Context) (interface{}, codes.Code)

const (
	keyRequestID = "request_id"
)

func HandleRequest(params interface{}, do DoFunc) func(c *gin.Context) {
	binders := []dataProcessor{&bodyBinder{}, &queryBinder{}, &uriBinder{}, &validator{}}
	return func(c *gin.Context) {
		p := reflect.New(reflect.TypeOf(params).Elem()).Elem().Addr().Interface()
		reqID := tools.GenUUID()

		//=========绑定参数
		for _, b := range binders {
			if err := b.process(c, p); err != nil {
				log.Error("binder process error:", err.UnWrapError())
				ginReply(c, reqID, nil, err)
				return
			}
		}
		//处理请求
		ctx := log.AddTraceID(c, reqID)
		res, err := do(ctx, p)
		if err != nil {
			log.ErrorfWithContext(ctx, "do request [%s %s] error:%s", c.Request.Method, c.Request.URL, err.UnWrapError())
		}
		ginReply(c, reqID, res, err)
	}
}

func HandleRequest_File(params interface{}, do DoFunc_File) func(c *gin.Context) {
	return func(c *gin.Context) {
		reqID := tools.GenUUID()
		//处理请求
		ctx := log.AddTraceID(c, reqID)
		res, err := do(ctx, c)
		if err != nil {
			log.ErrorfWithContext(ctx, "do request [%s %s] error:%s", c.Request.Method, c.Request.URL, err.UnWrapError())
		}
		ginReply(c, reqID, res, err)
	}
}
func HandleRequest_File_NoResponse(params interface{}, do DoFunc_File) func(c *gin.Context) {
	return func(c *gin.Context) {
		reqID := tools.GenUUID()
		//处理请求
		ctx := log.AddTraceID(c, reqID)
		res, err := do(ctx, c)
		if err != nil {
			log.ErrorfWithContext(ctx, "do request [%s %s] error:%s", c.Request.Method, c.Request.URL, err.UnWrapError())
		}
		ginReply1(c, reqID, res, err)
	}
}
func HandleRequest_Excel(params interface{}, do DoFunc_Excel) func(c *gin.Context) {
	binders := []dataProcessor{&bodyBinder{}, &queryBinder{}, &uriBinder{}, &validator{}}
	return func(c *gin.Context) {
		p := reflect.New(reflect.TypeOf(params).Elem()).Elem().Addr().Interface()
		reqID := tools.GenUUID()

		//=========绑定参数
		for _, b := range binders {
			if err := b.process(c, p); err != nil {
				log.Error("binder process error:", err.UnWrapError())
				ginReply(c, reqID, nil, err)
				return
			}
		}
		//处理请求
		ctx := log.AddTraceID(c, reqID)
		res, err := do(ctx, p, c)
		if err != nil {
			log.ErrorfWithContext(ctx, "do request [%s %s] error:%s", c.Request.Method, c.Request.URL, err.UnWrapError())
		}
		ginReply1(c, reqID, res, err)
	}
}

// func HandleRequest_Excel(params interface{}, do DoFunc_File) func(c *gin.Context) {
// 	return func(c *gin.Context) {
// 		p1 := reflect.New(reflect.TypeOf(params).Elem()).Elem().Addr().Interface()
// 		reqID := tools.GenUUID()
// 		//处理请求
// 		ctx := log.AddTraceID(c, reqID)
// 		res, err := do(ctx, c)
// 		if err != nil {
// 			log.ErrorfWithContext(ctx, "do request [%s %s] error:%s", c.Request.Method, c.Request.URL, err.UnWrapError())
// 		}
// 		ginReply1(c, reqID, res, err)
// 	}
// }

func ginReply1(c *gin.Context, requestID string, data interface{}, err codes.Code) {
	c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
	c.Writer.Header().Set("Access-Control-Max-Age", "86400")
	c.Writer.Header().Set("Access-Control-Allow-Methods", "*")
	c.Writer.Header().Set("Access-Control-Allow-Headers", "Authorization,Accept-Language, token, Origin, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token")
	c.Writer.Header().Set("Access-Control-Expose-Headers", "*")
	c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
	// c.JSON(200, NewResponse(requestID, data, err))
}

func ginReply(c *gin.Context, requestID string, data interface{}, err codes.Code) {
	c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
	c.Writer.Header().Set("Access-Control-Max-Age", "86400")
	c.Writer.Header().Set("Access-Control-Allow-Methods", "*")
	c.Writer.Header().Set("Access-Control-Allow-Headers", "Authorization,Accept-Language, token, Origin, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token")
	c.Writer.Header().Set("Access-Control-Expose-Headers", "*")
	c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
	c.JSON(200, NewResponse(requestID, data, err))
}

//解决跨域
func Cors() gin.HandlerFunc {
	return func(c *gin.Context) {
		method := c.Request.Method               //请求方法
		origin := c.Request.Header.Get("Origin") //请求头部
		// var headerKeys []string                  // 声明请求头keys
		// for k, _ := range c.Request.Header {
		// 	headerKeys = append(headerKeys, k)
		// }
		// headerStr := strings.Join(headerKeys, ", ")
		// if headerStr != "" {
		// 	headerStr = fmt.Sprintf("access-control-allow-origin, access-control-allow-headers, %s", headerStr)
		// } else {
		// 	headerStr = "access-control-allow-origin, access-control-allow-headers"
		// }
		if origin != "" {
			origin := c.Request.Header.Get("Origin")
			c.Header("Access-Control-Allow-Origin", origin) // 这是允许访问所有域
			c.Header("Access-Control-Allow-Methods", "*")   //服务器支持的所有跨域请求的方法,为了避免浏览次请求的多次'预检'请求
			//  header的类型
			c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma")
			//              允许跨域设置                                                                                                      可以返回其他子段
			c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar") // 跨域关键设置 让浏览器可以解析
			c.Header("Access-Control-Max-Age", "172800")                                                                                                                                                           // 缓存请求信息 单位为秒
			c.Header("Access-Control-Allow-Credentials", "false")                                                                                                                                                  //  跨域请求是否需要带cookie信息 默认设置为true
			c.Set("content-type", "application/json")                                                                                                                                                              // 设置返回格式是json
		}

		//放行所有OPTIONS方法
		if method == "OPTIONS" {
			c.JSON(http.StatusOK, "Options Request!")
		}
		// 处理请求
		c.Next() //  处理请求
	}
}

//
type dataProcessor interface {
	process(c *gin.Context, ptr interface{}) codes.Code
}

type bodyBinder struct{}

func (b *bodyBinder) process(c *gin.Context, ptr interface{}) codes.Code {
	data, err := c.GetRawData()
	if err != nil {
		log.ErrorWithContext(c, "get http bode error:", err.Error())
		return codes.ErrGetHTTPBodyFailed.Wrap(err)
	}

	if len(data) == 0 {
		return nil
	}

	//log.Info(string(data))
	if errBind := json.Bind(data, ptr); errBind != nil {
		log.ErrorfWithContext(c, "bind json error:%s", errBind.Error())
		return codes.ErrInvalidParams.WrapWithDetails(err, "bind json error,please check http body", errBind.Error())
	}
	return nil
}

type queryBinder struct{}

func (b *queryBinder) process(c *gin.Context, ptr interface{}) codes.Code {
	if err := c.ShouldBindQuery(ptr); err != nil {
		log.ErrorWithContext(c, "bind query error:", err.Error())
		return codes.ErrInvalidParams.WrapWithDetails(err, "bind query error,please check http query", err.Error())
	}
	return nil
}

type uriBinder struct{}

func (b *uriBinder) process(c *gin.Context, ptr interface{}) codes.Code {
	if err := c.ShouldBindUri(ptr); err != nil {
		log.ErrorWithContext(c, "bind uri error:", err.Error())
		return codes.ErrInvalidParams.WrapWithDetails(err, "bind uri error,please check http uri", err.Error())
	}
	return nil
}

type validator struct{}

func (b *validator) process(c *gin.Context, ptr interface{}) codes.Code {
	if err := validate(ptr); err != nil {
		return codes.ErrInvalidParams.WrapWithDetails(err, "validate params error,please check input", err.Error())
	}
	return nil
}
